package se.topshelf.golftracker.web.controller;

import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import se.topshelf.golftracker.core.domain.BaseEntity;
import se.topshelf.golftracker.core.service.BaseService;

import java.util.*;

/**
 *
 */
public abstract class BaseEntityController<T extends BaseEntity> {
    // View URLs
    public static final String VIEW_ALL_URL = "viewAll.htm";
    public static final String VIEW_URL = "view.htm";
    public static final String CREATE_URL = "create.htm";
    public static final String EDIT_URL = "edit.htm";
    public static final String SAVE_URL = "save.htm";
    public static final String REMOVE_URL = "remove.htm";
    // Model attributes
    public static final String ALL_ENTITIES_MODEL_ATTRIBUTE = "allEntities";
    public static final String ENTITY_MODEL_ATTRIBUTE = "entity";
    public static final String SUCCESS_MESSAGES_MODEL_ATTRIBUTE = "successMessages";
    public static final String ERROR_MESSAGES_MODEL_ATTRIBUTE = "errorMessages";
    // View names
    public static final String VIEW_ALL_ENTITIES_VIEW_NAME = "viewAll";
    public static final String VIEW_ENTITY_VIEW_NAME = "view";
    public static final String ADMIN_ENTITY_VIEW_NAME = "admin";
    public static final String CONTEXT_SEPARATOR = "/";
    public static final String PLEASE_SELECT_LIST_CHOICE_ENTRY = "--- Please select ---";
    public static final String NOT_AVAILABLE_LIST_CHOICE_ENTRY = "--- Not available ---";

    private Class<T> baseEntityClass;

    protected BaseEntityController(Class<T> baseEntityClass) {
        this.baseEntityClass = baseEntityClass;
    }

    @RequestMapping(VIEW_ALL_URL)
    public String viewAll(ModelMap model) {
        model.addAttribute(ALL_ENTITIES_MODEL_ATTRIBUTE, getBaseService().findAll(baseEntityClass));
        return buildViewNamePath(VIEW_ALL_ENTITIES_VIEW_NAME);
    }


    @RequestMapping(VIEW_URL)
    public String view(@RequestParam(value = "id", required = true) String entityId, ModelMap model) {
        model.addAttribute(ENTITY_MODEL_ATTRIBUTE, getBaseService().get(baseEntityClass, entityId));
        return buildViewNamePath(VIEW_ENTITY_VIEW_NAME);
    }

    @RequestMapping(value = CREATE_URL, method = RequestMethod.GET)
    public String createFormSetup(@RequestParam(required = false, value = "id") String parentId, ModelMap model) throws Exception {
        T newEntity = baseEntityClass.newInstance();
        model.addAttribute(ENTITY_MODEL_ATTRIBUTE, newEntity);
        if (parentId != null) {
            doAddParentToModel(model, parentId);
        }
        addModelReferenceData(model, newEntity);
        return buildViewNamePath(ADMIN_ENTITY_VIEW_NAME);
    }

    protected void doAddParentToModel(ModelMap model, String parentId) {
        // Does nothing by default, override if necessary to look up and set parent
    }

    @RequestMapping(value = EDIT_URL, method = RequestMethod.GET)
    public String editFormSetup(@RequestParam(value = "id", required = true) String entityId, ModelMap model) {
        T targetEntity = fetchTargetEntity(entityId);
        model.addAttribute(ENTITY_MODEL_ATTRIBUTE, targetEntity);
        addModelReferenceData(model, targetEntity);
        return buildViewNamePath(ADMIN_ENTITY_VIEW_NAME);
    }

    private T fetchTargetEntity(String entityId) {
        T targetEntity = (T) getBaseService().get(baseEntityClass, entityId);
        if (targetEntity == null) {
            throw new IllegalArgumentException(String.format("There exist no entry for id %s and class %s", entityId, baseEntityClass.getCanonicalName()));
        }
        return targetEntity;
    }

    @RequestMapping(value = SAVE_URL, method = RequestMethod.POST)
    public String save(@ModelAttribute(ENTITY_MODEL_ATTRIBUTE) T entityToSave,
                       BindingResult result, SessionStatus sessionStatus,
                       ModelMap model, RedirectAttributes redirectAttributes) {
        getValidator().validate(entityToSave, result);
        getBaseService().clearSession();
        if (result.hasErrors()) {
            // Go back to form to correct errors
            addModelReferenceData(model, entityToSave);
            return buildViewNamePath(ADMIN_ENTITY_VIEW_NAME);
        }
        doSave(entityToSave);
        sessionStatus.setComplete();
        redirectAttributes.addFlashAttribute(SUCCESS_MESSAGES_MODEL_ATTRIBUTE, buildMessageList("Save successful!"));
        return getRedirectAfterSavePath(entityToSave);
    }

    protected void doSave(T entityToSave) {
        // If need to do logic before save, override this
        getBaseService().saveOrUpdate(entityToSave);
    }

    protected String getRedirectAfterSavePath(T savedEntity) {
        return "redirect:/" + buildViewNamePath(VIEW_ALL_URL);
    }

    @RequestMapping(value = REMOVE_URL, method = RequestMethod.GET)
    public String remove(@RequestParam(value = "id", required = true) String entityId, RedirectAttributes redirectAttributes) {
        T entityToRemove = fetchTargetEntity(entityId);
        doRemove(entityToRemove);
        redirectAttributes.addFlashAttribute(SUCCESS_MESSAGES_MODEL_ATTRIBUTE, buildMessageList("Remove successful!"));
        return getRedirectAfterRemovePath(entityToRemove);
    }

    protected void doRemove(T entityToRemove) {
        // Override if necessary
        getBaseService().delete(entityToRemove);
    }

    protected String getRedirectAfterRemovePath(T removedEntity) {
        // Override if necessary
        return "redirect:/" + buildViewNamePath(VIEW_ALL_URL);
    }

    protected List<String> buildMessageList(String... messages) {
        return Arrays.asList(messages);
    }

    protected abstract Validator getValidator();

    protected String buildViewNamePath(String viewFileName) {
        return getEntityUrlContextPath() + CONTEXT_SEPARATOR + viewFileName;
    }

    protected abstract String getEntityUrlContextPath();

    protected abstract BaseService getBaseService();

    protected abstract void addModelReferenceData(ModelMap model, T entity);


    protected <S extends BaseEntity> Map<String, String> fetchAllEntitiesAndBuildReferenceDataFor(Class<S> baseEntityClass) {
        Map<String, String> allEntitiesReferenceDataMap = new HashMap<String, String>();

        SortedSet<S> allEntities = new TreeSet<S>(getBaseService().findAll(baseEntityClass));
        if (allEntities.size() > 0) {
            allEntitiesReferenceDataMap.put("", PLEASE_SELECT_LIST_CHOICE_ENTRY);

            for (S currEntity : allEntities) {
                allEntitiesReferenceDataMap.put(currEntity.getId(), currEntity.getGuiDescription());
            }
        } else {
            allEntitiesReferenceDataMap.put("", NOT_AVAILABLE_LIST_CHOICE_ENTRY);
        }
        return allEntitiesReferenceDataMap;
    }

}
